/*
 *  Copyright 2005 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.service.core.impl;

import com.blandware.atleap.persistence.core.ContentFieldValueDAO;
import com.blandware.atleap.persistence.core.MenuDAO;
import com.blandware.atleap.common.util.RegExUtil;
import com.blandware.atleap.common.util.ConvertUtil;
import com.blandware.atleap.common.Constants;
import com.blandware.atleap.model.core.ContentFieldValue;
import com.blandware.atleap.model.core.ContentField;
import com.blandware.atleap.model.core.MenuItem;
import com.blandware.atleap.service.core.UtilManager;

import java.util.List;
import java.util.Iterator;

import org.apache.commons.validator.GenericValidator;
import org.apache.oro.text.regex.Perl5Compiler;
import org.apache.oro.text.regex.MalformedPatternException;

/**
 * Implementation of {@link UtilManager}. 
 *
 * <p><a href="UtilManagerImpl.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.5 $ $Date: 2007/12/12 11:04:46 $
 */
public class UtilManagerImpl extends BaseManagerImpl implements UtilManager {

    /**
     * Application context path -- needed for correct replacement of URIs
     */
    protected String contextPath = null;

    /**
     * Application host -- needed for correct replacement of URIs
     */
    protected String host = null;

    /**
     * Application HTTP port -- needed for correct replacement of URIs
     */
    protected Integer httpPort = null;

    /**
     * Application HTTPS port -- needed for correct replacement of URIs
     */
    protected Integer httpsPort = null;

    /**
     * DAO to use with content field values
     */
    protected ContentFieldValueDAO contentFieldValueDAO;

    /**
     * DAO to use with menu items
     */
    protected MenuDAO menuDAO;

    /**
     * Sets context path used for correct URI replacing
     *
     * @param contextPath path to set
     */
    public void setContextPath(String contextPath) {
        this.contextPath = contextPath;
    }

    /**
     * Sets host name used for correct URI replacing
     *
     * @param host host name to set
     */
    public void setHost(String host) {
        this.host = host;
    }

    /**
     * Sets application HTTP port used for correct URI replacing
     *
     * @param httpPort HTTP port to set
     */
    public void setHttpPort(Integer httpPort) {
        this.httpPort = httpPort;
    }

    /**
     * Sets application HTTPS port used for correct URI replacing
     *
     * @param httpsPort HTTPS port to set
     */
    public void setHttpsPort(Integer httpsPort) {
        this.httpsPort = httpsPort;
    }

    /**
     * Sets DAO to use with content field values
     *
     * @param contentFieldValueDAO DAO to set
     */
    public void setContentFieldValueDAO(ContentFieldValueDAO contentFieldValueDAO) {
        this.contentFieldValueDAO = contentFieldValueDAO;
    }

    /**
     * Sets DAO to use with content field values
     *
     * @param menuDAO DAO to set
     */
    public void setMenuDAO(MenuDAO menuDAO) {
        this.menuDAO = menuDAO;
    }

    /**
     * Builds a regular expression for URI
     *
     * @param uriWithoutSlash   prepared URI (currently preparation is just
     *                          removing leading slash)
     * @param postfix           postfix of URI; this can be, for instance, a dot
     *                          followed with extension, or just an empty string
     * @param noExtension       if <code>true</code>, then no locale suffix
     *                          will be allowed
     * @return regular expression for given URI
     */
    protected String buildUriRegex(String uriWithoutSlash, String postfix, boolean noExtension) {
        return RegExUtil.escapeMetasymbols(uriWithoutSlash)
               + buildLocaleSuffixRegex(noExtension)
               + RegExUtil.escapeMetasymbols(postfix);
    }

    /**
     * Prepares URI for building regular expression. Currently just removes a
     * leading slash.
     *
     * @param uri   URI to be prepared
     * @return prepared URI
     */
    protected String prepareUri(String uri) {
        String oldUriWithoutSlash;
        oldUriWithoutSlash = uri;
        if (oldUriWithoutSlash.startsWith("/")) {
            oldUriWithoutSlash = oldUriWithoutSlash.substring("/".length());
        }
        return oldUriWithoutSlash;
    }

    /**
     * Builds a regular expression for locale suffix.
     *
     * @param noExtension   if <code>true</code>, no locale suffix will be allowed
     * @return locale suffix regular expression
     */
    protected String buildLocaleSuffixRegex(boolean noExtension) {
        return noExtension ? "()" : "(\\.[a-zA-Z]{2})?";
    }

    /**
     * Computes a postfix for URI. If <code>noExtension</code> is true, result
     * will be empty string, else result will be a dot followed with a common
     * extension determined from constants.
     *
     * @param noExtension whether no extension need to be added
     * @return URI postfix
     */
    protected String computePostfix(boolean noExtension) {
        String mappingPostfix = Constants.ACTION_MAPPING;
        if (mappingPostfix.startsWith("*.")) {
            mappingPostfix = mappingPostfix.substring("*.".length());
        }
        return (noExtension || GenericValidator.isBlankOrNull(mappingPostfix)) ? "" : "." + mappingPostfix;
    }

    /**
     * Builds a regular expression for RW prefix (which may be inserted between
     * context path and URI).
     *
     * @param rwPrefix  RW prefix to use
     * @return RW prefix regular expression
     */
    protected String buildRwPrefixRegex(String rwPrefix) {
        if (GenericValidator.isBlankOrNull(rwPrefix)) {
            rwPrefix = "";
        }
        if (!"/".equals(rwPrefix) && !"".equals(rwPrefix)) {
            rwPrefix += "/";
        } else {
            rwPrefix = "";
        }
        if (rwPrefix.startsWith("/")) {
            rwPrefix = rwPrefix.substring(1);
        }
        return "(" + RegExUtil.escapeMetasymbols(rwPrefix) + ")?";
    }

    /**
     * Builds a regular expression for context path.
     *
     * @return context path regular expression
     */
    protected String buildContextPathRegex() {
        boolean isContextPathPresent = contextPath != null;
        String context = contextPath;

        String contextPathRegex = "()";
        if (isContextPathPresent) {
            if ("".equals(context)) {
                context = "/";
            }
            if (!"/".equals(context)) {
                context += "/";
            }
            contextPathRegex = "(" + RegExUtil.escapeMetasymbols(context) + ")?";
        }
        return contextPathRegex;
    }

    /**
     * Builds a regular expression for our host. 'Our host' means host name
     * of server on which we are running with one of ports, that are assigned to
     * our application (or no port if our port is standard one).
     *
     * @return our host regular expression
     */
    protected String buildHostRegex() {
        // TODO: may be it's better to pass port numbers in some other way?
        int httpPortNumber = httpPort == null ? 80 : httpPort.intValue();
        int httpsPortNumber = httpsPort == null ? 443 : httpsPort.intValue();

        String httpPortRegex = httpPortNumber == 80 ? "(\\:80)?" : "(\\:" + RegExUtil.escapeMetasymbols(String.valueOf(httpPortNumber)) + ")";
        String httpsPortRegex = httpsPortNumber == 443 ? "(\\:443)?" : "(\\:" + RegExUtil.escapeMetasymbols(String.valueOf(httpsPortNumber)) + ")";
        String portRegex = "(" + httpPortRegex + "|" + httpsPortRegex + ")";

        return "((https?\\:\\/\\/" + RegExUtil.escapeMetasymbols(host) + portRegex + ")?)";
    }

    /**
     * Builds a regular expression for query string inside a href attribute,
     * for example (with leading ? sign).
     *
     * @return query string regular expression
     */
    protected String buildQueryStringRegex() {
        return "(\\?[^\"\\s]*)?";
    }

    /**
     * @see com.blandware.atleap.service.core.UtilManager#replaceLinkableObjectUriInLinkedObjects(String, String, java.util.List, java.util.List, String, boolean)
     */
    public void replaceLinkableObjectUriInLinkedObjects(String oldUri, String newUri,
                                                        List linkedContentFieldValues,
                                                        List linkedMenuItems, String rwPrefix,
                                                        boolean noExtension) {

        String hostRegex = buildHostRegex();
        String contextPathRegex = buildContextPathRegex();
        String rwPrefixRegex = buildRwPrefixRegex(rwPrefix);

        // remove leading slashes
        String oldUriWithoutSlash = prepareUri(oldUri);
        String newUriWithoutSlash = prepareUri(newUri);

        // compute postfix
        String postfix = computePostfix(noExtension);

        // regex for URI
        String uriRegex = buildUriRegex(oldUriWithoutSlash, postfix, noExtension);
        // regex for whole tag
        String regex = "(<([\\w]+)\\b[^>]*\\b(href|src|background)=(\\\")?" + hostRegex + contextPathRegex + rwPrefixRegex + ")" + uriRegex + "(" + buildQueryStringRegex() + "(\\\")?[^>]*>)";
        // replacement string for whole <a> tag
        String replace = "$1" + newUriWithoutSlash + "$12" + postfix + "$13";

        // process content field values
        for (Iterator i = linkedContentFieldValues.iterator(); i.hasNext();) {
            ContentFieldValue value = (ContentFieldValue) i.next();
            ContentField field = value.getContentField();
            byte type = field.getType();

            // get content
            String content;
            if (type == ContentField.LINE_TYPE) {
                content = value.getSimpleValue();
            } else {
                content = ConvertUtil.convertToString(value.getValue());
            }
            // do replace
            try {
                content = RegExUtil.replaceAll(content, regex, replace, Perl5Compiler.CASE_INSENSITIVE_MASK);
            } catch (MalformedPatternException e) {
                log.error("Bad regexp", e);
            }

            // set changed content
            if (type == ContentField.LINE_TYPE) {
                value.setSimpleValue(content);
            } else {
                value.setValue(ConvertUtil.convertToByteArray(content));
            }

            // save content field value
            contentFieldValueDAO.updateContentFieldValue(value, field, value.getContentLocale());
        }

        // process menu items
        for (Iterator i = linkedMenuItems.iterator(); i.hasNext();) {
            MenuItem item = (MenuItem) i.next();
            // skip menu items defined in configuration files
            if (item.isDynamic()) {
                String location = item.getLocation();
                String image = item.getImage();
                String altImage = item.getAltImage();
                if (!GenericValidator.isBlankOrNull(location)) {
                    try {
                        if (location != null) {
                            location = RegExUtil.replaceAll(location, "(" + hostRegex + contextPathRegex + rwPrefixRegex + ")" + uriRegex, "$1" + newUriWithoutSlash + "$9" + postfix, Perl5Compiler.CASE_INSENSITIVE_MASK);
                        }
                        String uriRegexWithoutExt = RegExUtil.escapeMetasymbols(oldUriWithoutSlash);
                        if (image != null) {
                            image = RegExUtil.replaceAll(image, "(" + hostRegex + contextPathRegex + rwPrefixRegex + ")" + uriRegexWithoutExt, "$1" + newUriWithoutSlash, Perl5Compiler.CASE_INSENSITIVE_MASK);
                        }
                        if (altImage != null) {
                            altImage = RegExUtil.replaceAll(altImage, "(" + hostRegex + contextPathRegex + rwPrefixRegex + ")" + uriRegexWithoutExt, "$1" + newUriWithoutSlash, Perl5Compiler.CASE_INSENSITIVE_MASK);
                        }
                    } catch (MalformedPatternException e) {
                        log.error("Bad regexp", e);
                    }
                    item.setLocation(location);
                    item.setImage(image);
                    item.setAltImage(altImage);
                    menuDAO.updateMenuItem(item, item.getParentItem(), item.getOwner());
                }
            }
        }
    }

    /**
     * @see com.blandware.atleap.service.core.UtilManager#replaceImageSizesInLinkedObjects(String,java.util.List,int,int,int,int,String,boolean)
     */
    public void replaceImageSizesInLinkedObjects(String uri, List linkedContentFieldValues,
                                                 int oldWidth, int oldHeight,
                                                 int newWidth, int newHeight,
                                                 String rwPrefix, boolean noExtension) {
        String hostRegex = buildHostRegex();
        String contextPathRegex = buildContextPathRegex();
        String rwPrefixRegex = buildRwPrefixRegex(rwPrefix);

        // remove leading slash
        String uriWithoutSlash = prepareUri(uri);

        // compute postfix
        String postfix = computePostfix(noExtension);

        // regex for URI
        String uriRegex = buildUriRegex(uriWithoutSlash, postfix, noExtension);
        // regex for whole URL (without query string!)
        String urlRegex = hostRegex + contextPathRegex + rwPrefixRegex + uriRegex;
        // regex for part of tag that is before some attribute
        String betweenAttrsRegex = "[^>]*\\b";
        // regex for query string
        String queryStringRegex = buildQueryStringRegex();
        // regex for whole <img> tag
        String widthRegexL = "(<img\\b" + betweenAttrsRegex + ")width=\\\"?" + oldWidth + "\\\"?(" + betweenAttrsRegex + "src=\\\"?" + urlRegex + queryStringRegex + "\\\"?[^>]*>)";
        String widthReplaceL = "$1width=\"" + newWidth + "\"$2";
        String widthRegexR = "(<img\\b" + betweenAttrsRegex + "src=\\\"?" + urlRegex + queryStringRegex + "\\\"?" + betweenAttrsRegex + ")width=\\\"?" + oldWidth + "\\\"?(" + "[^>]*>)";
        String widthReplaceR = "$1width=\"" + newWidth + "\"$11";
        String heightRegexL = "(<img\\b" + betweenAttrsRegex + ")height=\\\"?" + oldHeight + "\\\"?(" + betweenAttrsRegex + "src=\\\"?" + urlRegex + queryStringRegex + "\\\"?[^>]*>)";
        String heightReplaceL = "$1height=\"" + newHeight + "\"$2";
        String heightRegexR = "(<img\\b" + betweenAttrsRegex + "src=\\\"?" + urlRegex + queryStringRegex + "\\\"?" + betweenAttrsRegex + ")height=\\\"?" + oldHeight + "\\\"?(" + "[^>]*>)";
        String heightReplaceR = "$1height=\"" + newHeight + "\"$11";

        // replace sizes for each linked CFV
        for (Iterator i = linkedContentFieldValues.iterator(); i.hasNext();) {
            ContentFieldValue contentFieldValue = (ContentFieldValue) i.next();
            ContentField contentField = contentFieldValue.getContentField();
            byte type = contentField.getType();
            String content;
            if (type == ContentField.LINE_TYPE) {
                content = contentFieldValue.getSimpleValue();
            } else {
                content = ConvertUtil.convertToString(contentFieldValue.getValue());
            }
            try {
                content = RegExUtil.replaceAll(content, widthRegexL, widthReplaceL, Perl5Compiler.CASE_INSENSITIVE_MASK);
                content = RegExUtil.replaceAll(content, widthRegexR, widthReplaceR, Perl5Compiler.CASE_INSENSITIVE_MASK);
                content = RegExUtil.replaceAll(content, heightRegexL, heightReplaceL, Perl5Compiler.CASE_INSENSITIVE_MASK);
                content = RegExUtil.replaceAll(content, heightRegexR, heightReplaceR, Perl5Compiler.CASE_INSENSITIVE_MASK);
            } catch (MalformedPatternException e) {
                log.error("Bad regexp", e);
            }
            if (type == ContentField.LINE_TYPE) {
                contentFieldValue.setSimpleValue(content);
            } else {
                contentFieldValue.setValue(ConvertUtil.convertToByteArray(content));
            }
            contentFieldValueDAO.updateContentFieldValue(contentFieldValue, contentField, contentFieldValue.getContentLocale());
        }
    }
}
